// UsbEject version 1.0 March 2006
// written by Simon Mourier <email: simon [underscore] mourier [at] hotmail [dot] com>

namespace UsbEject.Library
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Text;

    /// <summary>
    /// A generic base class for physical devices.
    /// </summary>
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class Device : IComparable
    {
        private string path;
        private DeviceClass deviceClass;
        private string description;
        private string className;
        private string classGuid;
        private Device parent;
        private int index;
        private DeviceCapabilities capabilities = DeviceCapabilities.None;
        private List<Device> removableDevices;
        private string friendlyName;
        private Native.SP_DEVINFO_DATA deviceInfoData;

        internal Device(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index)
        {
            if (deviceClass == null)
                throw new ArgumentNullException("deviceClass", "DeviceClass object cannot be null");

            if (deviceInfoData == null)
                throw new ArgumentNullException("deviceInfoData", "DeviceInfoData object cannot be null");

			this.deviceClass = deviceClass;
			this.path = path; // may be null
			this.deviceInfoData = deviceInfoData;
			this.index = index;
        }

        /// <summary>
        /// Gets the device's index.
        /// </summary>
        public int Index
        {
            get
            {
                return this.index;
            }
        }

        /// <summary>
        /// Gets the device's class instance.
        /// </summary>
        [Browsable(false)]
        public DeviceClass DeviceClass
        {
            get
            {
				return this.deviceClass;
            }
        }

        /// <summary>
        /// Gets the device's path.
        /// </summary>
        public string Path
        {
            get
            {
				if (this.path == null)
                {
                }

				return this.path;
            }
        }

        /// <summary>
        /// Gets the device's instance handle.
        /// </summary>
        public int InstanceHandle
        {
            get
            {
				return this.deviceInfoData.devInst;
            }
        }

        /// <summary>
        /// Gets the device's class name.
        /// </summary>
        public string Class
        {
            get
            {
				if (this.className == null)
                {
					this.className = this.deviceClass.GetProperty(this.deviceInfoData, Native.SPDRP_CLASS, null);
                }

				return this.className;
            }
        }

        /// <summary>
        /// Gets the device's class Guid as a string.
        /// </summary>
        public string ClassGuid
        {
            get
            {
                if (string.IsNullOrEmpty(this.classGuid))
                {
					this.classGuid = this.deviceClass.GetProperty(this.deviceInfoData, Native.SPDRP_CLASSGUID, null);
                }

				return this.classGuid;
            }
        }

        /// <summary>
        /// Gets the device's description.
        /// </summary>
        public string Description
        {
            get
            {
				if (this.description == null)
                {
					this.description = this.deviceClass.GetProperty(this.deviceInfoData, Native.SPDRP_DEVICEDESC, null);
                }

				return this.description;
            }
        }

        /// <summary>
        /// Gets the device's friendly name.
        /// </summary>
        public string FriendlyName
        {
            get
            {
				if (this.friendlyName == null)
                {
					this.friendlyName = this.deviceClass.GetProperty(this.deviceInfoData, Native.SPDRP_FRIENDLYNAME, null);
                }

				return this.friendlyName;
            }
        }

        /// <summary>
        /// Gets the device's capabilities.
        /// </summary>
        public DeviceCapabilities Capabilities
        {
            get
            {
                if (this.capabilities == DeviceCapabilities.None)
                {
					this.capabilities = (DeviceCapabilities)this.deviceClass.GetProperty(this.deviceInfoData, Native.SPDRP_CAPABILITIES, 0);
                }

				return this.capabilities;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this device is a USB device.
        /// </summary>
        public virtual bool IsUsb
        {
            get
            {
				if (this.Class == "USB")
				{
					return true;
				}

				if (this.Parent == null)
				{
					return false;
				}

				return this.Parent.IsUsb;
            }
        }

        /// <summary>
        /// Gets the device's parent device or null if this device has not parent.
        /// </summary>
        public Device Parent
        {
            get
            {
				if (this.parent == null)
                {
                    int parentDevInst = 0;
					int hr = Native.CM_Get_Parent(ref parentDevInst, this.deviceInfoData.devInst, 0);
                    if (hr == 0)
                    {
						this.parent = new Device(this.deviceClass, this.deviceClass.GetInfo(parentDevInst), null, -1);
                    }
                }

				return this.parent;
            }
        }

        /// <summary>
        /// Gets this device's list of removable devices.
        /// Removable devices are parent devices that can be removed.
        /// </summary>
		public virtual List<Device> RemovableDevices
        {
            get
            {
                if (this.removableDevices == null)
                {
					this.removableDevices = new List<Device>();

					if ((this.Capabilities & DeviceCapabilities.Removable) != 0)
                    {
						this.removableDevices.Add(this);
                    }
                    else
                    {
						if (this.Parent != null)
                        {
							foreach (Device device in this.Parent.RemovableDevices)
                            {
								this.removableDevices.Add(device);
                            }
                        }
                    }
                }

				return this.removableDevices;
            }
        }

        /// <summary>
        /// Ejects the device.
        /// </summary>
        /// <param name="allowUI">Pass true to allow the Windows shell to display any related UI element, false otherwise.</param>
        /// <returns>null if no error occured, otherwise a contextual text.</returns>
        public string Eject(bool allowUI)
        {
			foreach (Device device in this.RemovableDevices)
            {
                if (allowUI)
                {
					// don't handle errors, there should be a UI for this
					Native.CM_Request_Device_Eject_NoUi(device.InstanceHandle, IntPtr.Zero, null, 0, 0);
                }
                else
                {
                    StringBuilder sb = new StringBuilder(1024);

                    Native.PNP_VETO_TYPE veto;
                    int hr = Native.CM_Request_Device_Eject(device.InstanceHandle, out veto, sb, sb.Capacity, 0);
                    if (hr != 0)
                        throw new Win32Exception(hr);

                    if (veto != Native.PNP_VETO_TYPE.Ok)
                        return veto.ToString();
                }
            }

			return null;
        }

        /// <summary>
        /// Compares the current instance with another object of the same type.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>A 32-bit signed integer that indicates the relative order of the comparands.</returns>
        public virtual int CompareTo(object obj)
        {
            Device device = obj as Device;
			
			if (device == null)
			{
				throw new ArgumentNullException("obj", "Comparison object cannot be null");
			}

            return this.Index.CompareTo(device.Index);
        }
    }
}
